from stacking.instance.generation.simgen import SimGen
from stacking.warehouse import Warehouse
from stacking.instance import Instance


class SimGenNR(SimGen):
    """Simulation-based instance generator that uses Normalized Relocations to assess the 
    difficulty of an instance. Each iteration, an instance is solved and the total number of 
    relocations is divided by the number of items, and this result is used to estimate difficulty.
    """
    def configure(self, target=1.0, epsilon=0.1, solve_fnc=None, config_fnc=None):
        if solve_fnc is None:
            raise Exception("missing keyword argument solve_fnc")
        self.solve_fnc = solve_fnc
        self.target = target
        self.epsilon = epsilon
        if config_fnc is not None:
            config_fnc(self)
            
    def analyze(self, items, nstacks):
        """Returns True if the given item list matches the specified NR interval (controlled by 
        'target' and 'epsilon'). If the observed NR is too different from the target (more than 
        the allowed epsilon), the production interval is adjusted and the method returns False."""
        NR = normalized_relocations(self.solve_fnc, items, nstacks)
        delta = abs(NR - self.target)
        self.obs_tseries.collect(NR)
        self.tgt_tseries.collect(self.target)
        if delta <= self.epsilon:
            return True, delta
        # adjust production interval: if the target NR is 10x the observed value, then the 
        # "target" production interval is one tenth of the current interval in order to increase 
        # difficulty roughly 10 times.
        self.adjust_production_interval(self.production_interval * NR / self.target)
        return False, delta
        
        
def normalized_relocations(solve_fnc, items, nstacks):
    """This solution evaluation method actually creates an instance of the SP and solves it, 
    returning the total number of relocations divided by the number of items. The higher this 
    number, the harder the instance should be. Of course this is dependent on the solution method 
    used, but intuition tells us that instance difficulty is obviously related to the number 
    of relocations necessary to solve it.
    NOTE: the 'solve_fnc' argument should be a callable taking an SP instance as (only) argument, 
    and should return the number of relocations executed."""
    warehouse = Warehouse.quick_build(id="NR", nstacks=nstacks)
    instance = Instance(items, warehouse)
    relocations = solve_fnc(instance)
    return float(relocations) / len(items)
    
SimGen.NR = SimGenNR
